﻿using System;
using System.Collections;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Configuration;
using System.Drawing.Design;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Permissions;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using System.Xml;
using System.Xml.XPath;

namespace SCS.Web.UI.WebControls
{
    /// <summary>
    /// Represents the HierList hierarchical list WebControl.
    /// </summary>
    #region Class Attributes
    [AspNetHostingPermission(SecurityAction.Demand, Level = AspNetHostingPermissionLevel.Minimal)]
    [AspNetHostingPermission(SecurityAction.InheritanceDemand, Level = AspNetHostingPermissionLevel.Minimal)]
    [DefaultEvent("ItemClicked"), DefaultProperty("Items")]
    [ToolboxData("<{0}:HierList runat=\"server\"> </{0}:HierList>")]
    [ParseChildren(true, "Items")]
    //[Designer(typeof(SCS.Web.UI.WebControls.Design.HierListDesigner))]
    #endregion
    public class HierList : HierarchicalDataBoundControl, IPostBackEventHandler
    {
        #region Fields

        private HierListItemBindingCollection _bindings = null;        
        private HierListItem _rootItem = null;
        private HierListItem _selectedItem = null;

        private string _currentSiteMapNodeUrl;
                
        private bool _dataBound;

        private bool _isRootNode = true;
        private bool _isDataSourceXml = false;

        private XPathDocument _xpathDoc;
        private object _xmlDataSource;

        private const string _defaultNodeText = "Node";

        #endregion

        /// <summary>
        /// Handles the <see cref="E:System.Web.UI.Control.Load"/> event.
        /// </summary>
        /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains event data.</param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);            
        }

        protected override void OnPreRender(EventArgs e)
        {
            if (!_dataBound && _xmlDataSource != null && _xpathDoc != null && !Page.IsPostBack)
            {
                OnDataBinding(EventArgs.Empty);
                PerformDataBinding();
                OnDataBound(EventArgs.Empty);
            }

            base.OnPreRender(e);
        }

        /// <summary>
        /// Renders the HTML opening tag of the control to the specified writer. This method is used primarily by control developers.
        /// </summary>
        /// <param name="writer">A <see cref="T:System.Web.UI.HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>
        public override void RenderBeginTag(HtmlTextWriter writer)
        {
            AddAttributesToRender(writer);

            if (ListType == HierListType.Unordered)
            {
                writer.RenderBeginTag(HtmlTextWriterTag.Ul);
            }
            else
            {
                writer.RenderBeginTag(HtmlTextWriterTag.Ol);
            }
        }

        /// <summary>
        /// Renders the control to the specified HTML writer.
        /// </summary>
        /// <param name="writer">The <see cref="T:System.Web.UI.HtmlTextWriter"/> object that receives the control content.</param>
        protected override void Render(HtmlTextWriter writer)
        {
            PrepareControlsForRender();
            base.Render(writer);
        }

        /// <summary>
        /// Prepares the child controls for render.
        /// </summary>
        protected virtual void PrepareControlsForRender()
        {

        }

        /// <summary>
        /// Renders the contents of the control to the specified writer. This method is used primarily by control developers.
        /// </summary>
        /// <param name="writer">A <see cref="T:System.Web.UI.HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>
        protected override void RenderContents(HtmlTextWriter writer)
        {
            PrepareControlsForRender();
            RenderControlHierarchy(writer, Items);
        }

        /// <summary>
        /// Renders the control hierarchy.
        /// </summary>
        /// <param name="writer">The writer.</param>
        /// <param name="items">The items.</param>
        protected virtual void RenderControlHierarchy(HtmlTextWriter writer, HierListItemCollection items)
        {
            for (int index = 0; index < items.Count; index++)
            {
                items[index].Render(writer);

                if (items[index].Items.Count > 0)
                {                    
                    if (ListType == HierListType.Unordered)
                    {
                        writer.RenderBeginTag(HtmlTextWriterTag.Ul);
                    }
                    else
                    {
                        writer.RenderBeginTag(HtmlTextWriterTag.Ol);
                    }

                    RenderControlHierarchy(writer, items[index].Items);
                    writer.RenderEndTag();
                }

                writer.RenderEndTag(); // close ol/li tag                
            }
        }

        /// <summary>
        /// Called by the ASP.NET page framework to notify server controls that use composition-based implementation to create any child controls they contain in preparation for posting back or rendering.
        /// </summary>
        protected override void CreateChildControls()
        {
            base.CreateChildControls();

            int level = -1;
            CreateControlHierarchy(RootItem, Items, ref level);
        }

        /// <summary>
        /// Creates the control hierarchy.
        /// </summary>
        /// <param name="parent">The parent.</param>
        /// <param name="items">The items.</param>
        /// <param name="level">The level.</param>
        protected virtual void CreateControlHierarchy(HierListItem parent, HierListItemCollection items, ref int level)
        {
            level++;

            for (int index = 0; index < items.Count; index++)
            {
                HierListItem item = items[index];
                item.ControlRoot = this;
                
                item.ID = string.Format("{0}_{1}_{2}", this.UniqueID, level, index);

                if (item.Items.Count > 0)
                    CreateControlHierarchy(item, item.Items, ref level);
            }
        }

        /// <summary>
        /// Binds data from the data source to the control.
        /// </summary>
        protected override void PerformDataBinding()
        {
            base.PerformDataBinding();

            if (IsTrackingViewState)
            {
                ((IStateManager)RootItem).TrackViewState();
            }
                        
            if (_xpathDoc != null)
            {
                //OnDataBinding(EventArgs.Empty);

                XPathNavigator navigator = _xpathDoc.CreateNavigator();
                XPathNodeIterator iterator = navigator.SelectChildren(XPathNodeType.Element);

                DataBindRecursive(RootItem, iterator);

                //OnDataBound(EventArgs.Empty);
            }
            else
            {
                //HierarchicalDataSourceView data = this.GetData(this.RootItem.DataPath);
                HierarchicalDataSourceView data = this.GetData("");

                if (base.IsBoundUsingDataSourceID || (this.DataSource != null))
                {
                    if (data == null)
                    {
                        throw new InvalidOperationException("Data source cannot be null.");
                    }

                    IHierarchicalEnumerable enumerable = data.Select();
                    this.RootItem.Items.Clear();

                    if (enumerable != null)
                    {
                        if (base.IsBoundUsingDataSourceID)
                        {
                            SiteMapDataSource dataSource = this.GetDataSource() as SiteMapDataSource;

                            if (dataSource != null)
                            {
                                SiteMapNode currentNode = dataSource.Provider.CurrentNode;

                                if (currentNode != null)
                                    this._currentSiteMapNodeUrl = currentNode.Url;
                            }
                            else
                            {
                                _isDataSourceXml = true;
                            }
                        }

                        try
                        {
                            this.DataBindRecursive(RootItem, enumerable);
                        }
                        finally
                        {
                            this._currentSiteMapNodeUrl = null;
                        }
                    }
                }
            }

            _dataBound = true;

            CreateChildControls();
        }

        private void DataBindRecursive(HierListItem node, IHierarchicalEnumerable enumerable)
        {
            int depth = node.Depth + 1;

            if (depth <= this.MaximumDepth || this.MaximumDepth == -1)
            {
                foreach (object data in enumerable)
                {
                    string text = _defaultNodeText;
                    string value = null;
                    string navigateUrl = "";                 
                    string target = "";                                    
                    string toolTip = "";
                    string dataMember = "";

                    bool? enabled = null;
                    bool? selectable = null;

                    IHierarchyData hierarchyData = enumerable.GetHierarchyData(data);
                    dataMember = hierarchyData.Type;
                    
                    HierListItemBinding binding = this.DataBindings.GetBinding(dataMember, depth);

                    if (binding != null)
                    {
                        // we're binding to an xml file

                        #region Binding

                        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(data);

                        text = GetBindingValue(data, binding, properties, binding.TextField, true);

                        if (string.IsNullOrEmpty(text))
                            text = binding.Text;

                        if (!string.IsNullOrEmpty(binding.FormatString))
                            text = string.Format(binding.FormatString, text);

                        value = GetBindingValue(data, binding, properties, binding.ValueField, false);

                        if (string.IsNullOrEmpty(value))
                            value = binding.Value;

                        target = GetBindingValue(data, binding, properties, binding.TargetField, false);

                        if (string.IsNullOrEmpty(target))
                            target = binding.Target;
                        
                        navigateUrl = GetBindingValue(data, binding, properties, binding.NavigateUrlField, false);

                        if (string.IsNullOrEmpty(navigateUrl))
                            navigateUrl = binding.NavigateUrl;

                        toolTip = GetBindingValue(data, binding, properties, binding.ToolTipField, false);

                        if (string.IsNullOrEmpty(toolTip))
                            toolTip = binding.ToolTip;

                        enabled = GetBindingValue(data, binding, properties, binding.EnabledField);

                        if (enabled != null)
                            enabled = binding.Enabled;

                        selectable = GetBindingValue(data, binding, properties, binding.SelectableField);

                        if (selectable != null)
                            selectable = binding.Selectable;

                        #endregion
                    }
                    else if (data is INavigateUIData)  
                    {
                        // we're binding to a sitemap file

                        INavigateUIData siteMapNode = (INavigateUIData)data;

                        text = siteMapNode.Name;
                        value = siteMapNode.Value;
                        navigateUrl = siteMapNode.NavigateUrl;
                        selectable = (string.IsNullOrEmpty(navigateUrl));                        
                        toolTip = siteMapNode.Description;
                    }
                       
                    HierListItem child = CreateItemInternal();

                    child.Text = text;
                    child.Value = value;
                    child.Url = navigateUrl;
                    child.Target = target;                        
                    child.ToolTip = toolTip;                        
                    child.Enabled = enabled ?? true;
                    child.Selectable = selectable ?? true;   
                    child.SetDataPath(hierarchyData.Path);
                    child.Depth = depth;
                    child.SetDataBound(true);

                    if (_isRootNode && _isDataSourceXml)
                    {
                        _rootItem = child;
                        ((IStateManager)child).TrackViewState();

                        _isRootNode = false;
                    }
                    else
                    {
                        node.Items.Add(child);
                    }
                        
                    if (string.Equals(hierarchyData.Path, this._currentSiteMapNodeUrl, StringComparison.OrdinalIgnoreCase))
                    {
                        child.Selected = true;
                    }
                                                
                    child.SetDataItem(hierarchyData.Item);                        
                    child.SetDataItem(null);

                    OnItemDataBound(new HierItemEventArgs(child));

                    if (hierarchyData.HasChildren)
                    {
                        IHierarchicalEnumerable children = hierarchyData.GetChildren();

                        if (children != null)
                        {
                            this.DataBindRecursive(child, children);
                        }
                    }                    
                }
            }
        }

        private void DataBindRecursive(HierListItem node, XPathNodeIterator iterator)
        {
            int depth = node.Depth + 1;

            if (depth <= this.MaximumDepth || this.MaximumDepth == -1)
            {
                while (iterator.MoveNext())
                {
                    string text = _defaultNodeText;
                    string value = null;
                    string navigateUrl = "";
                    string target = "";
                    string toolTip = "";

                    bool? enabled = null;
                    bool? selectable = null;

                    foreach (HierListItemBinding binding in DataBindings)
                    {
                        if (binding.DataMember.Equals(iterator.Current.Name, StringComparison.InvariantCultureIgnoreCase))
                        {
                            if (!string.IsNullOrEmpty(binding.TextField))
                            {
                                text = iterator.Current.GetAttribute(binding.TextField, "");

                                if (string.IsNullOrEmpty(text))
                                    text = binding.Text;

                                if (!string.IsNullOrEmpty(binding.FormatString))
                                    text = string.Format(binding.FormatString, text);
                            }
                            
                            if (!string.IsNullOrEmpty(binding.ValueField))
                            {
                                value = iterator.Current.GetAttribute(binding.ValueField, "");

                                if (string.IsNullOrEmpty(value))
                                    value = binding.Value;
                            }
                            
                            if (!string.IsNullOrEmpty(binding.NavigateUrlField))
                            {
                                navigateUrl = iterator.Current.GetAttribute(binding.NavigateUrlField, "");

                                if (string.IsNullOrEmpty(navigateUrl))
                                    navigateUrl = binding.NavigateUrl;
                            }
                            
                            if (!string.IsNullOrEmpty(binding.TargetField))
                            {
                                target = iterator.Current.GetAttribute(binding.TargetField, "");

                                if (string.IsNullOrEmpty(target))
                                    target = binding.Target;
                            }
                            
                            if (!string.IsNullOrEmpty(binding.ToolTipField))
                            {
                                toolTip = iterator.Current.GetAttribute(binding.ToolTipField, "");

                                if (string.IsNullOrEmpty(toolTip))
                                    toolTip = binding.ToolTip;
                            }

                            bool tempBool;

                            if (!string.IsNullOrEmpty(binding.EnabledField))
                            {
                                if (!bool.TryParse(binding.EnabledField, out tempBool))
                                {
                                    enabled = binding.Enabled ?? true;
                                }
                            }

                            if (!string.IsNullOrEmpty(binding.SelectableField))
                            {
                                if (!bool.TryParse(binding.SelectableField, out tempBool))
                                {
                                    selectable = binding.Selectable ?? true;
                                }
                            }
                        }
                    }

                    HierListItem child = null;

                    if (text != null || value != null)
                    {
                        child = CreateItemInternal();

                        child.Text = text;
                        child.Value = value;
                        child.Url = navigateUrl;
                        child.Target = target;

                        if (toolTip.Length > 0)
                            child.ToolTip = toolTip;

                        child.Enabled = enabled ?? true;
                        child.Selectable = selectable ?? true;
                        child.SetDataBound(true);
                        //child.SetDataPath(iterator);
                        child.Depth = depth;

                        if (_isRootNode)
                        {
                            _rootItem = child;
                            ((IStateManager)child).TrackViewState();

                            _isRootNode = false;
                        }
                        else
                        {
                            node.Items.Add(child);
                        }
                    }

                    XPathNavigator itemNavigator = iterator.Current.Clone();

                    if (iterator.Current.HasChildren)
                    {
                        XPathNodeIterator childIterator = iterator.Current.SelectChildren(XPathNodeType.Element);

                        DataBindRecursive(child, childIterator);
                    }
                }
            }
        }

        private string GetBindingValue(object data, HierListItemBinding binding, PropertyDescriptorCollection properties, string fieldName, bool localize)
        {
            string value = "";

            if (fieldName.Length > 0)
            {
                PropertyDescriptor descriptor = properties.Find(fieldName, true);

                if (descriptor != null)
                {
                    object descriptValue = descriptor.GetValue(data);

                    if (descriptValue != null)
                    {
                        if (localize && binding.FormatString.Length > 0)
                        {
                            value = string.Format(CultureInfo.CurrentCulture, binding.FormatString, new object[] { descriptValue });
                        }
                        else
                        {
                            value = descriptValue.ToString();
                        }
                    }
                }
            }

            return value;
        }

        private bool? GetBindingValue(object data, HierListItemBinding binding, PropertyDescriptorCollection properties, string fieldName)
        {            
            if (fieldName.Length > 0)
            {
                PropertyDescriptor descriptor = properties.Find(fieldName, true);

                if (descriptor == null)
                {
                    throw new InvalidOperationException(string.Format("Invalid data binding for {0}.", fieldName));
                }

                object o = descriptor.GetValue(data);

                if (o != null && o is bool)
                {        
                    return (bool)o;
                }

                bool tempValue;

                if (bool.TryParse(o.ToString(), out tempValue))
                {
                    return tempValue;
                }                
            }

            return binding.Enabled;
        }

        internal HierListItem CreateItemInternal()
        {
            HierListItem item = CreateItem();
            item.ControlRoot = this;
            return item;
        }

        private void SetSelectedItem(HierListItem node, string value)
        {
            if (node.Value.Equals(value, StringComparison.CurrentCultureIgnoreCase))
            {
                node.Selected = true;
                _selectedItem = node;                
            }
            else
            {
                node.Selected = false;                   

                foreach (HierListItem item in node.Items)
                {
                    SetSelectedItem(item, value);
                }
            }
        }

        /// <summary>
        /// Creates a list item.
        /// </summary>
        /// <returns></returns>
        protected virtual HierListItem CreateItem()
        {
            return new HierListItem();
        }

        #region Event Stuff

        private static readonly object _eventSubmitKey = new object();

        /// <summary>
        /// Occurs when a list item is clicked.
        /// </summary>
        [Category("Action"), Description("Raised when the user clicks a button.")]
        public event HierItemClickedHandler ItemClicked
        {
            add
            {
                Events.AddHandler(_eventSubmitKey, value);
            }
            remove
            {
                Events.RemoveHandler(_eventSubmitKey, value);
            }
        }

        /// <summary>
        /// Raises the <see cref="E:ItemClicked"/> event.
        /// </summary>
        /// <param name="e">The <see cref="SCS.Web.UI.WebControls.HierItemEventArgs"/> instance containing the event data.</param>
        protected virtual void OnItemClicked(HierItemEventArgs e)
        {
            HierItemClickedHandler handler = (HierItemClickedHandler)Events[_eventSubmitKey];

            if (handler != null)
            {
                handler(this, new HierItemEventArgs(_selectedItem));
            }
        }

        private static readonly object _eventItemDataBoundtKey = new object();

        /// <summary>
        /// Occurs when a list item data bound to the data source.
        /// </summary>
        [Category("Action"), Description("Raised when an item is data bound.")]
        public event HierItemClickedHandler ItemDataBound
        {
            add
            {
                Events.AddHandler(_eventItemDataBoundtKey, value);
            }
            remove
            {
                Events.RemoveHandler(_eventItemDataBoundtKey, value);
            }
        }

        /// <summary>
        /// Raises the <see cref="E:ItemDataBound"/> event.
        /// </summary>
        /// <param name="e">The <see cref="SCS.Web.UI.WebControls.HierItemEventArgs"/> instance containing the event data.</param>
        protected virtual void OnItemDataBound(HierItemEventArgs e)
        {
            HierItemClickedHandler handler = (HierItemClickedHandler)Events[_eventItemDataBoundtKey];

            if (handler != null)
            {
                handler(this, e);
            }
        }

        private static readonly object _eventLoadingViewStateKey = new object();

        /// <summary>
        /// Occurs prior to the view state loading.
        /// </summary>
        /// <remarks>Use this event for restoring the view state from a persistence other than the page.</remarks>
        [Category("Action"), Description("Raised before view state is loaded. Attach to this event to save view state to another cache other than view state.")]
        public event ViewStateEventHandler ViewStateLoading
        {
            add
            {
                Events.AddHandler(_eventLoadingViewStateKey, value);
            }
            remove
            {
                Events.RemoveHandler(_eventLoadingViewStateKey, value);
            }
        }

        /// <summary>
        /// Raises the <see cref="E:ViewStateLoading"/> event.
        /// </summary>
        /// <param name="e">The <see cref="SCS.Web.UI.WebControls.ViewStateEventArgs"/> instance containing the event data.</param>
        /// <returns><c>true</c> if event is wired; otherwise, <c>false</c>.</returns>
        protected virtual bool OnViewStateLoading(ViewStateEventArgs e)
        {
            ViewStateEventHandler handler = (ViewStateEventHandler)Events[_eventLoadingViewStateKey];

            if (handler != null)
            {
                handler(this, e);
                return true;
            }
            else
            {
                return false;
            }
        }

        private static readonly object _eventSavingViewStateKey = new object();

        /// <summary>
        /// Occurs prior to view state saving.
        /// </summary>
        /// <remarks>Use this event for storing the view state in a persistence store instead of within the page.</remarks>
        [Category("Action"), Description("Raised before view state is saved. Attach to this event to load view state from another cache source. ")]
        public event ViewStateEventHandler ViewStateSaving
        {
            add
            {
                Events.AddHandler(_eventSavingViewStateKey, value);
            }
            remove
            {
                Events.RemoveHandler(_eventSavingViewStateKey, value);
            }
        }

        /// <summary>
        /// Raises the <see cref="E:ViewStateSaving"/> event.
        /// </summary>
        /// <param name="e">The <see cref="SCS.Web.UI.WebControls.ViewStateEventArgs"/> instance containing the event data.</param>
        /// <returns></returns>
        protected virtual bool OnViewStateSaving(ViewStateEventArgs e)
        {
            ViewStateEventHandler handler = (ViewStateEventHandler)Events[_eventSavingViewStateKey];

            if (handler != null)
            {
                handler(this, e);
                return true;
            }
            else
            {
                return false;
            }
        }

        #endregion

        #region Properties

        #region Misc

        /// <summary>
        /// Gets the list items for adding and editing.
        /// </summary>
        /// <value>The list items.</value>
        [Category("Misc"), Description("The collection of list items."), Bindable(false)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true)]
        [PersistenceMode(PersistenceMode.EncodedInnerDefaultProperty)]
        public HierListItemCollection Items
        {
            get
            {
                return RootItem.Items;
            }
        }

        /// <summary>
        /// Gets the root item for the list.
        /// </summary>
        /// <value>The root item.</value>
        internal HierListItem RootItem
        {
            get
            {
                if (_rootItem == null)
                {
                    _rootItem = CreateItemInternal();

                    if (IsTrackingViewState)
                    {
                        ((IStateManager)_rootItem).TrackViewState();
                    }
                }
                return _rootItem;
            }
        }

        #endregion

        #region Behavior
        /// <summary>
        /// Gets or sets the maximum depth for the list.
        /// </summary>
        /// <value>The maximum depth.</value>
        [Category("Behavior"), Themeable(true), Description("The maximum number of levels to display."), DefaultValue(-1)]
        public int MaximumDepth
        {
            get
            {
                object o = this.ViewState["MaximumDepth"];               
                return (o != null) ? (int)o : -1;
            }
            set
            {
                if (value < 0)
                {
                    throw new ArgumentOutOfRangeException("MaximumDynamicDisplayLevels", "Maximum depth must be greater than zero or -1 to disable.");
                }

                this.ViewState["MaximumDepth"] = value;

                if (this._dataBound)
                {
                    this._dataBound = false;
                    this.PerformDataBinding();
                }
            }
        }

        /// <summary>
        /// Gets or sets the type of the list as a bullet or numeric list.
        /// </summary>
        /// <value>The type of the list.</value>
        [Category("Behavior"), Description("The type of list to rendered (numbered or bulleted outline."), Bindable(true), DefaultValue(HierListType.Unordered)]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Content), NotifyParentProperty(true)]
        [PersistenceMode(PersistenceMode.Attribute)]
        public HierListType ListType
        {
            get
            {
                object o = ViewState["ListType"];
                return (o != null) ? (HierListType)o : HierListType.Unordered;
            }
            set
            {
                ViewState["ListType"] = value;
            }
        }

        #endregion

        /// <summary>
        /// Gets a collection of all data bindings on the control. 
        /// </summary>
        /// <value></value>
        /// <returns>The collection of data bindings.</returns>
        /// <remarks>Use the binding to map date elements in the data source to the properties of the list items.</remarks>
        [MergableProperty(false), DefaultValue((string)null), PersistenceMode(PersistenceMode.InnerProperty)]
        [Category("Data"), Description("The data binding mappings between data source and item properties.")]        
        public HierListItemBindingCollection DataBindings
        {
            get
            {
                if (this._bindings == null)
                {
                    this._bindings = new HierListItemBindingCollection(this);

                    if (base.IsTrackingViewState)
                    {
                        ((IStateManager)this._bindings).TrackViewState();
                    }
                }
                return this._bindings;
            }
        }

        /// <summary>
        /// Gets or sets the currently selected item.
        /// </summary>
        /// <value>The selected item.</value>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public HierListItem SelectedItem
        {
            get { return _selectedItem; }
            set { _selectedItem = value; }
        }

        /// <summary>
        /// Gets or sets the currently selected value.
        /// </summary>
        /// <value>The selected value.</value>
        [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        public string SelectedValue
        {
            get 
            {
                if (_selectedItem == null)
                    return "";

                return _selectedItem.Value; 
            }
            set 
            {
                _selectedItem = null;
                SetSelectedItem(RootItem, value); 
            }
        }

        /// <summary>
        /// Gets or sets the object from which the data-bound control retrieves its list of data items.
        /// </summary>
        /// <value></value>
        /// <returns>An object that represents the data source from which the data-bound control retrieves its data. The default is null.</returns>
        public override object DataSource
        {
            get
            {
                return (_xmlDataSource == null) ? base.DataSource : _xmlDataSource;
            }
            set
            {
                if (value is XPathDocument)
                {
                    _xpathDoc = (XPathDocument)value;
                    _xmlDataSource = value;
                }
                else if (value is XmlDocument)
                {
                    MemoryStream stream = new MemoryStream();
                    ((XmlDocument)value).Save(stream);
                    stream.Position = 0;
                    _xpathDoc = new XPathDocument(stream);
                    _xmlDataSource = value;
                }
                else if (value is String)
                {
                    _xpathDoc = new XPathDocument((string)value);
                    _xmlDataSource = value;
                }
                else if (value is Stream)
                {
                    _xpathDoc = new XPathDocument((Stream)value);
                    _xmlDataSource = value;
                }
                else if (value is TextReader)
                {
                    _xpathDoc = new XPathDocument((TextReader)value);
                    _xmlDataSource = value;
                }
                else if (value is XmlReader)
                {
                    _xpathDoc = new XPathDocument((XmlReader)value);
                    _xmlDataSource = value;
                }
                else
                {
                    base.DataSource = value;
                }
            }
        }
        
        #endregion
        
        #region INamingContainter Implementation (View State)

        /// <summary>
        /// Saves any state that was modified after the <see cref="M:System.Web.UI.WebControls.Style.TrackViewState"/> method was invoked.
        /// </summary>
        /// <returns>
        /// An object that contains the current view state of the control; otherwise, if there is no view state associated with the control, null.
        /// </returns>
        protected override object SaveViewState()
        {
            object[] state = new object[2];

            state[0] = base.SaveViewState();
            state[1] = (Items.Count > 0) ? ((IStateManager)Items).SaveViewState() : null;

            // Another performance optimization. If no modifications were made to any
            // properties from their persisted state, the view state for this control
            // is null. Returning null, rather than an array of null values helps
            // minimize the view state significantly.

            ViewStateEventArgs args = new ViewStateEventArgs(state);
            
            if (OnViewStateSaving(args))
            {                
                return new object[]{null, null};
            }
            else
            {
                for (int i = 0; i < 2; i++)
                    if (state[i] != null)
                        return state;

                return null;
            }
        }
        /// <summary>
        /// Restores view-state information from a previous request that was saved with the <see cref="M:System.Web.UI.WebControls.WebControl.SaveViewState"/> method.
        /// </summary>
        /// <param name="savedState">An object that represents the control state to restore.</param>
        protected override void LoadViewState(object savedState)
        {
            ViewStateEventArgs args = new ViewStateEventArgs(null);
            OnViewStateLoading(args);

            if (args.StateInfo != null)
            {
                savedState = args.StateInfo;
            }

            object baseState = null;
            object[] state = null;

            if (savedState != null)
            {
                state = (object[])savedState;
                baseState = state[0];
            }

            // Always call the base class, even if the state is null, so
            // the base class gets a chance to fully implement its LoadViewState
            // functionality.
            base.LoadViewState(baseState);

            if (state == null)
                return;

            if (state[1] != null)
                ((IStateManager)Items).LoadViewState(state[1]);
        }
        /// <summary>
        /// Causes the control to track changes to its view state so they can be stored in the object's <see cref="P:System.Web.UI.Control.ViewState"/> property.
        /// </summary>
        protected override void TrackViewState()
        {
            base.TrackViewState();

            if (_rootItem != null)
                ((IStateManager)Items).TrackViewState();
        }

        #endregion

        #region IPostBackEventHandler Implementation

        /// <summary>
        /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server.
        /// </summary>
        /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param>
        public void RaisePostBackEvent(string eventArgument)
        {
            _selectedItem = null;
            SetSelectedItem(RootItem, eventArgument);
            OnItemClicked(new HierItemEventArgs(_selectedItem));
        }

        #endregion
    }
}